/*
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
*/



//I need to add the image/x-iff in  /usr/share/kde4/services/imagethumbnail.desktop
//for thumbnails to work.


#include "iff.h"

#include <assert.h>

#include <QColor>
#include <QtGui/QImage>
#include <QtCore/QDataStream>

#include <kdebug.h>

typedef quint32 uint;
typedef quint16 ushort;
typedef quint8 uchar;


namespace {	// Private.

static QDataStream & operator>> ( QDataStream & s, BMHDChunk & head )
{
    s >> head.Width;
    s >> head.Height;
    s >> head.XOrigin;
    s >> head.YOrigin;
    s >> head.Bitplanes;
    s >> head.Mask;
    s >> head.Compression;
    s >> head.Padding;
    s >> head.Transparency;
    s >> head.Aspect;
    s >> head.PageWidth;
    s >> head.PageHeight;
    /*
    qDebug() << "Width: " << head.Width << " - Height: " << head.Height << " - XOrigin: " << head.XOrigin;
    qDebug() << "YOrigin: " << head.YOrigin << " - bitplanes: " << head.Bitplanes << " - mask: " << head.Mask;
    qDebug() << "Compression: " << head.Compression << " - Padding: " << head.Padding << " - Transparency:" << head.Transparency
             << " - Aspect:" << head.Aspect << " - PageWidth: " << head.PageWidth << " - PageHeight: " << head.PageHeight;
     */
    return s;
}

//static bool IsSupported( const BMHDChunk &BMHD )
//{
//    return BMHD.Width*BMHD.Height!=0;
//}




} // namespace


IFFHandler::IFFHandler()
{
    chunky=cmap=NULL;
}

bool IFFHandler::canRead() const
{
    if (canRead(device())) {
        setFormat("iff");
        return true;
    }
    return false;
}

void IFFHandler::feedPlanar(quint8 value)
{
    static int c=0;
    int d ;

    if (BMHD.Bitplanes!=24) {
        d= (ypos*BMHD.Width)+(xpos);
        chunky[d]   |= (((value&128)==0)?0:1<<(depth));
        chunky[d+1] |= (((value& 64)==0)?0:1<<(depth));
        chunky[d+2] |= (((value& 32)==0)?0:1<<(depth));
        chunky[d+3] |= (((value& 16)==0)?0:1<<(depth));
        chunky[d+4] |= (((value&  8)==0)?0:1<<(depth));
        chunky[d+5] |= (((value&  4)==0)?0:1<<(depth));
        chunky[d+6] |= (((value&  2)==0)?0:1<<(depth));
        chunky[d+7] |= (((value&  1)==0)?0:1<<(depth));
        xpos += 8;
        if (xpos >= BMHD.Width) {
            depth++;
            if( depth >= BMHD.Bitplanes ) {
                depth = 0;
                ypos++;
            }
            xpos = 0;
        }
    } else {
        d= (ypos*BMHD.Width)+(3*xpos+c);
        chunky[d]   =value;
        ++c;
        if (c==3) {
            c=0;
            xpos += 1;
            if (xpos >= BMHD.Width) {
                ypos++;
                xpos = 0;
            }
        }
    }


}

//#pragma GCC diagnostic ignored "-Wmultichar"
//pragma does not work :(
#include <QMessageBox>
bool IFFHandler::read(QImage *outImage)
{
    int annoc=0;
    char *annotation;
    bool isInterlaced=false;
    QDataStream s( device() );
    //s.setByteOrder( QDataStream::LittleEndian );
    s.setByteOrder( QDataStream::BigEndian );

    quint32 t;
    s >> t;
    if (t!='FORM')
        return false;

    quint32 FileLength;
    s >> FileLength;

    s>>t;
    if (t!='ILBM')
        return false;
    FileLength-=4;


    while(FileLength>0) {
        s>>t;
        switch(t) {
        FileLength-=4;
        case 'BMHD':
            int bmhdlen;
            s >>bmhdlen;
            s >>BMHD;
            qDebug() << "bitplanes: "  << BMHD.Bitplanes;
            qDebug() << "compression: "  << BMHD.Compression;
            qDebug() << "masking: " << BMHD.Mask;
            //qDebug() << BMHD.Width << "  %8=" << BMHD.Width%16;
            if (BMHD.Width%16) {
                BMHD.Width += (16-BMHD.Width%16);
            }
            FileLength -= bmhdlen;
            chunky = new quint8[ BMHD.Width*BMHD.Height*BMHD.Bitplanes];
            memset(chunky,0,BMHD.Width*BMHD.Height*BMHD.Bitplanes);
            depth=xpos=ypos=0;
            break;
        case 'CMAP':
            int cmaplen;
            s >> cmaplen;
            FileLength -= cmaplen;
            qDebug() << "cmaplen=" << cmaplen;
            cmap = (quint8 *)new quint8[cmaplen*3];
            memset(cmap,0,cmaplen*3);
            for( int x=0; x<(cmaplen/3); x++ ) {
                s >> cmap[(x*3)+0];
                s >> cmap[(x*3)+1];
                s >> cmap[(x*3)+2];
                //qDebug() << "cmap["<<x<<"] = " << cmap[(x*3)+0] << "," << cmap[(x*3)+1] << "," << cmap[(x*3)+2];
            }
            break;
        case 'DPI ':
            int dpilen;
            quint8 consume;
            s >> dpilen;
            FileLength -= dpilen;
            for(int i=0; i<dpilen; ++i) s >> consume;
            break;
        case 'GRAB':
            int grablen;
            s >> grablen;
            FileLength -= grablen;
            for(int i=0; i<grablen; ++i) s >> consume;
            qDebug() << "in grab...";
            break;
        case 'ANNO':
            int annolen;
            s >> annolen;
            FileLength -= annolen;
            annotation= new char[FileLength+1];
            annoc=0;
            for(int i=0; i<annolen; ++i) {
                s >> consume;
                annotation[annoc++]=(char)consume;
            }
            annotation[annoc]='\0';
            qDebug() << "Annotation: "<<annotation;
            delete [] annotation;
            break;
        case 'DPPS':
            int dppslen;
            s >> dppslen;
            FileLength -= dppslen;
            for(int i=0; i<dppslen; ++i) s >> consume;
            qDebug() << "in dpps...";
            break;
        case 'CRNG':
            int crnglen;
            s >> crnglen;
            FileLength -= crnglen;
            for(int i=0; i<crnglen; ++i) s >> consume;
            qDebug() << "in crng...";
            break;
        case 'DRNG':
            int drnglen;
            s >> drnglen;
            FileLength -= drnglen;
            for(int i=0; i<drnglen; ++i) s >> consume;
            qDebug() << "in drng...";
            break;
        case 'CAMG':
            int camglen;
            s >> camglen;
            FileLength -= camglen;
            int camgdata;
            s >> camgdata;
            if (camgdata & 4)
                isInterlaced = true;
            qDebug() <<"CAMG: "<< (void *)camgdata;
            if (camgdata & hiRes)
                qDebug() << " Hires";
            if (camgdata & lace)
                qDebug() << " Interlaced";
            if (camgdata & extraHalfbrite)
                qDebug() << " Extra Half-Bright";
            if (camgdata & ham)
                qDebug() << " HAM";
            break;
        case 'BODY':
        {
            int bodylen;
            s >> bodylen;
            int bytes_wanted = ((BMHD.Width>>3)*BMHD.Height)*BMHD.Bitplanes;
            qDebug() << "bytes wanted : " << bytes_wanted;
            qDebug() << "bytes in file: " << bodylen;

            while( bytes_wanted > 0 ) {
                qint8 f;
                qint8 byte;

                s >> f;
                if (!BMHD.Compression) {
                    feedPlanar(f);
                    --bytes_wanted;
                } else
                    if( (f>=0) /*&& (f<=127)*/ ) {
                        /* copy the next n+1 bytes literally */
                        for( int n=0; n<=f; n++ ) {
                            s >> byte;
                            feedPlanar( byte);
                            bytes_wanted--;
                        }
                    } else if( (f<=-1) /* && (f>=-127)*/ ) {
                        /* replicate the next byte -n+1 times */
                        //unsigned char in = ReadByte();
                        s >> byte;
                        for( int n=0; n<=-f; n++ ) {
                            feedPlanar( byte);
                            bytes_wanted--;
                        }
                    } else {
                        qDebug() << "Error in iff decoding (body)...";
                        return false;
                        break;
                    }
            }



            //qDebug() << "Alive before color applying..." << BMHD.Width;
            QImage result(BMHD.Width,BMHD.Height, QImage::Format_ARGB32);


            quint8 c,r,g,b;
            for( int y=0; y<BMHD.Height; y++ ) {
                for( int x=0; x<BMHD.Width; x++ ) {
                    if (BMHD.Bitplanes!=24) {
                        c = chunky[x+(y*BMHD.Width)];
                        r = cmap[(c*3)+0];
                        g = cmap[(c*3)+1];
                        b = cmap[(c*3)+2];
                        result.setPixel(x,y,QColor(r,g,b).rgb());
                    } else {
                        //nope, 24bit does not work :(
                        r = chunky[x+(y*BMHD.Width)];
                        g = chunky[x+(y*BMHD.Width)+1];
                        b = chunky[x+(y*BMHD.Width)+2];
                        result.setPixel(x,y,QColor(r,g,b).rgb());
                    }
                }
            }

            *outImage=result;
            if (chunky) delete [] chunky;
            if (cmap) delete [] cmap;
            return true;
            break;
        }
        default:
        {
            qDebug() << "Unknown Envelop in iff : " <<  t;
            return false;
        }
        }
    }
    return false;
}

bool IFFHandler::write(const QImage &image)
{
    QDataStream s( device() );
    s.setByteOrder( QDataStream::LittleEndian );

    //const QImage& img = image;
    //    const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
    //    for( int i = 0; i < 12; i++ )
    //        s << targaMagic[i];

    //    // write header
    //    s << quint16( img.width() ); // width
    //    s << quint16( img.height() ); // height
    //    s << quint8( hasAlpha ? 32 : 24 ); // depth (24 bit RGB + 8 bit alpha)
    //    s << quint8( hasAlpha ? 0x24 : 0x20 ); // top left image (0x20) + 8 bit alpha (0x4)

    //    for( int y = 0; y < img.height(); y++ )
    //        for( int x = 0; x < img.width(); x++ ) {
    //            const QRgb color = img.pixel( x, y );
    //            s << quint8( qBlue( color ) );
    //            s << quint8( qGreen( color ) );
    //            s << quint8( qRed( color ) );
    //            if( hasAlpha )
    //                s << quint8( qAlpha( color ) );
    //        }

    return false;
}

QByteArray IFFHandler::name() const
{
    return "iff";
}

bool IFFHandler::canRead(QIODevice *device)
{

    if (!device) {
        qWarning("IFFHandler::canRead() called with no device");
        return false;
    }
    //qDebug() << "in canRead() ..." << (bool)(device->peek(4) == "\x46\x4f\x52\x4d") ;
    return device->peek(4) == "\x46\x4f\x52\x4d";
}


class IFFPlugin : public QImageIOPlugin
{
public:
    QStringList keys() const;
    Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
    QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
};

QStringList IFFPlugin::keys() const
{
    return QStringList() << "iff" << "IFF";
}

QImageIOPlugin::Capabilities IFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
    if (format == "iff" || format == "IFF")
        return Capabilities(CanRead);// | CanWrite);
    if (!format.isEmpty())
        return 0;
    if (!device->isOpen())
        return 0;

    Capabilities cap;
    if (device->isReadable() && IFFHandler::canRead(device))
        cap |= CanRead;
    //    if (device->isWritable())
    //        cap |= CanWrite;
    return cap;
}

QImageIOHandler *IFFPlugin::create(QIODevice *device, const QByteArray &format) const
{
    QImageIOHandler *handler = new IFFHandler;
    handler->setDevice(device);
    handler->setFormat(format);
    return handler;
}

Q_EXPORT_STATIC_PLUGIN(IFFPlugin)
Q_EXPORT_PLUGIN2(iff, IFFPlugin)
